package com.wxzhou.wangzhuan.security;

import com.wxzhou.wangzhuan.dto.UserInfoDto;
import com.wxzhou.wangzhuan.service.AuthService;
import com.wxzhou.wangzhuan.service.TokenService;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

/**
 * 自定义认证机制 - 支持可选认证
 * 当Token无效时，不抛出异常，返回null让业务逻辑继续执行
 */
@ApplicationScoped
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism {

    private static final Logger LOG = Logger.getLogger(CustomAuthenticationMechanism.class);

    @Inject
    TokenService tokenService;

    @Inject
    AuthService authService;

    public static final String DEVICE_HEADER = "device-id";
    public static final String CHANNEL_HEADER = "channel";

    @Override
    public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
        // 必须获取设备ID和渠道
        String deviceId = extractDevice(context); // 设备ID
        String channel = extractChannel(context); // 渠道

        // 校验设备ID和渠道（必须存在）
        if (deviceId == null || deviceId.trim().isEmpty()) {
            LOG.warn("设备ID不能为空");
            return Uni.createFrom().nullItem();
        }
        if (channel == null || channel.trim().isEmpty()) {
            LOG.warn("渠道不能为空");
            return Uni.createFrom().nullItem();
        }

        String token = extractToken(context);

        // 无论token是否存在，都必须执行validateAndBuildSecurityIdentity方法
        return validateAndBuildSecurityIdentity(token, deviceId, channel)
                .onFailure().invoke(throwable -> {
                    // 记录警告日志，但不阻断流程
                    String tokenInfo = token != null ? token.substring(0, Math.min(token.length(), 10)) + "..." : "null";
                    LOG.warn(String.format("Token验证失败，但允许继续执行 | deviceId=%s, channel=%s, token=%s", deviceId, channel, tokenInfo),throwable);
                })
                .onFailure().recoverWithNull(); // 失败时返回null而不是抛出异常
    }

    @Override
    public Uni<ChallengeData> getChallenge(RoutingContext context) {
        // 可选认证，不需要强制返回挑战
        return Uni.createFrom().nullItem();
    }

    @Override
    public Uni<Boolean> sendChallenge(RoutingContext context) {
        // 可选认证，不发送挑战
        return Uni.createFrom().item(false);
    }

    @Override
    public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
        return Uni.createFrom().item(new HttpCredentialTransport(HttpCredentialTransport.Type.AUTHORIZATION, "Bearer"));
    }

    @Override
    public int getPriority() {
        return HttpAuthenticationMechanism.super.getPriority();
    }


    private Uni<SecurityIdentity> validateAndBuildSecurityIdentity(String token, String deviceId, String channel) {
        // 无论token是否存在，都要执行验证流程
        if (token == null || token.trim().isEmpty()) {
            LOG.debug(String.format("Token为空，构建匿名身份 | deviceId=%s, channel=%s", deviceId, channel));
            return Uni.createFrom().item(buildSecurityIdentity(null, deviceId, channel));
        }

        return tokenService.validateToken(token)
                .onItem().transformToUni(tokenInfo -> {
                    if (tokenInfo.isPresent()) {
                        Long userId = Long.valueOf(tokenService.extractUserId(tokenInfo.get()));
                        String tokenDeviceId = tokenService.extractDeviceId(tokenInfo.get());

                        // 验证设备ID是否匹配（如果token中有设备ID）
                        if (tokenDeviceId != null && !tokenDeviceId.equals(deviceId)) {
                            LOG.warn(String.format("设备ID不匹配 | tokenDeviceId=%s, requestDeviceId=%s", tokenDeviceId, deviceId));
                            // 仍然继续，但使用请求的设备ID
                        }

                        if (userId != null) {
                            // 刷新token过期时间，然后获取用户信息
                            return tokenService.refreshToken(token)
                                    .onFailure().invoke(throwable -> {
                                        LOG.warn("刷新token失败，但允许继续执行 | token={}, error={}",
                                                token.substring(0, Math.min(token.length(), 10)) + "...", throwable);
                                    })
                                    .onFailure().recoverWithNull()
                                    .onItem().transformToUni(result ->
                                            // 获取用户信息
                                            authService.getUserInfo(userId)
                                                    .onItem().transform(userInfo -> {
                                                        // 无论是否获取到用户信息，都构建SecurityIdentity
                                                        return buildSecurityIdentity(userInfo, deviceId, channel);
                                                    })
                                                    .onFailure().invoke(throwable -> {
                                                        LOG.warn("获取用户信息失败，但允许继续执行 | userId={}, error={}", userId, throwable);
                                                    })
                                                    .onFailure().recoverWithUni(throwable ->
                                                            // 即使获取用户信息失败，也构建匿名身份
                                                            Uni.createFrom().item(buildSecurityIdentity(null, deviceId, channel))
                                                    )
                                    );
                        }
                    }
                    // token验证成功但没有用户ID，或者tokenInfo为空，构建匿名身份
                    return Uni.createFrom().item(buildSecurityIdentity(null, deviceId, channel));
                })
                .onFailure().recoverWithUni(throwable -> {
                    LOG.warn(String.format("Token验证失败，构建匿名身份 | deviceId=%s, channel=%s", deviceId, channel), throwable);
                    return Uni.createFrom().item(buildSecurityIdentity(null, deviceId, channel));
                });
    }

    private SecurityIdentity buildSecurityIdentity(UserInfoDto userInfo, String deviceId, String channel) {
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();

        if (userInfo != null) {
            // 有用户信息的情况
            builder.setPrincipal(new CustomPrincipal(
                    userInfo.getUserId(),
                    deviceId,
                    channel,  // 新增channel参数
                    userInfo.getUserLevel()
            )).addRole("user");

            // 添加用户属性
            if (userInfo.getNickName() != null) {
                builder.addAttribute("nickname", userInfo.getNickName());
            }
            if (userInfo.getUserLevel() != null) {
                builder.addAttribute("userLevel", userInfo.getUserLevel());
            }
        } else {
            // 匿名用户的情况
            builder.setPrincipal(new CustomPrincipal(
                    null,  // 匿名用户ID
                    deviceId,
                    channel,     // 新增channel参数
                    null         // 无用户等级
            )).addRole("guest"); // 匿名用户角色
        }

        // 添加设备ID和渠道作为属性（无论是否认证）
        builder.addAttribute("deviceId", deviceId);
        builder.addAttribute("channel", channel);

        return builder.build();
    }

    /**
     * 抽取token
     *
     * @param context
     * @return
     */
    private String extractToken(RoutingContext context) {
        // 从Authorization头提取token
        String authHeader = context.request().getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }

    private String extractDevice(RoutingContext context) {
        // 从Authorization头提取token
        String authHeader = context.request().getHeader(DEVICE_HEADER);
        if (authHeader != null && !authHeader.trim().isBlank()) {
            return authHeader.trim();
        }
        return null;
    }

    private String extractChannel(RoutingContext context) {
        // 从Authorization头提取token
        String authHeader = context.request().getHeader(CHANNEL_HEADER);
        if (authHeader != null && !authHeader.trim().isBlank()) {
            return authHeader.trim();
        }
        return null;
    }
}